// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/keyboard/content/keyboard_ui_content.h"

#include "base/command_line.h"
#include "base/macros.h"
#include "base/values.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui.h"
#include "content/public/common/bindings_policy.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/window.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/keyboard/content/keyboard_constants.h"
#include "ui/keyboard/keyboard_controller.h"
#include "ui/keyboard/keyboard_switches.h"
#include "ui/keyboard/keyboard_util.h"
#include "ui/wm/core/shadow.h"

namespace {

// The WebContentsDelegate for the keyboard.
// The delegate deletes itself when the keyboard is destroyed.
class KeyboardContentsDelegate : public content::WebContentsDelegate,
                                 public content::WebContentsObserver {
public:
    explicit KeyboardContentsDelegate(keyboard::KeyboardUIContent* ui)
        : ui_(ui)
    {
    }
    ~KeyboardContentsDelegate() override { }

private:
    // Overridden from content::WebContentsDelegate:
    content::WebContents* OpenURLFromTab(
        content::WebContents* source,
        const content::OpenURLParams& params) override
    {
        source->GetController().LoadURL(
            params.url, params.referrer, params.transition, params.extra_headers);
        Observe(source);
        return source;
    }

    bool CanDragEnter(content::WebContents* source,
        const content::DropData& data,
        blink::WebDragOperationsMask operations_allowed) override
    {
        return false;
    }

    bool ShouldCreateWebContents(
        content::WebContents* web_contents,
        int route_id,
        int main_frame_route_id,
        int main_frame_widget_route_id,
        WindowContainerType window_container_type,
        const std::string& frame_name,
        const GURL& target_url,
        const std::string& partition_id,
        content::SessionStorageNamespace* session_storage_namespace) override
    {
        return false;
    }

    bool IsPopupOrPanel(const content::WebContents* source) const override
    {
        return true;
    }

    void MoveContents(content::WebContents* source,
        const gfx::Rect& pos) override
    {
        aura::Window* keyboard = ui_->GetKeyboardWindow();
        // keyboard window must have been added to keyboard container window at this
        // point. Otherwise, wrong keyboard bounds is used and may cause problem as
        // described in crbug.com/367788.
        DCHECK(keyboard->parent());
        // keyboard window bounds may not set to |pos| after this call. If keyboard
        // is in FULL_WIDTH mode, only the height of keyboard window will be
        // changed.
        keyboard->SetBounds(pos);
    }

    // Overridden from content::WebContentsDelegate:
    void RequestMediaAccessPermission(
        content::WebContents* web_contents,
        const content::MediaStreamRequest& request,
        const content::MediaResponseCallback& callback) override
    {
        ui_->RequestAudioInput(web_contents, request, callback);
    }

    // Overridden from content::WebContentsObserver:
    void WebContentsDestroyed() override { delete this; }

    keyboard::KeyboardUIContent* ui_;

    DISALLOW_COPY_AND_ASSIGN(KeyboardContentsDelegate);
};

} // namespace

namespace keyboard {

class WindowBoundsChangeObserver : public aura::WindowObserver {
public:
    explicit WindowBoundsChangeObserver(KeyboardUIContent* ui)
        : ui_(ui)
    {
    }
    ~WindowBoundsChangeObserver() override { }

    void AddObservedWindow(aura::Window* window)
    {
        if (!window->HasObserver(this)) {
            window->AddObserver(this);
            observed_windows_.insert(window);
        }
    }
    void RemoveAllObservedWindows()
    {
        for (std::set<aura::Window*>::iterator it = observed_windows_.begin();
             it != observed_windows_.end(); ++it)
            (*it)->RemoveObserver(this);
        observed_windows_.clear();
    }

private:
    void OnWindowBoundsChanged(aura::Window* window,
        const gfx::Rect& old_bounds,
        const gfx::Rect& new_bounds) override
    {
        ui_->UpdateInsetsForWindow(window);
    }
    void OnWindowDestroyed(aura::Window* window) override
    {
        if (window->HasObserver(this))
            window->RemoveObserver(this);
        observed_windows_.erase(window);
    }

    KeyboardUIContent* ui_;
    std::set<aura::Window*> observed_windows_;

    DISALLOW_COPY_AND_ASSIGN(WindowBoundsChangeObserver);
};

KeyboardUIContent::KeyboardUIContent(content::BrowserContext* context)
    : browser_context_(context)
    , default_url_(kKeyboardURL)
    , window_bounds_observer_(new WindowBoundsChangeObserver(this))
{
}

KeyboardUIContent::~KeyboardUIContent()
{
    ResetInsets();
}

void KeyboardUIContent::LoadSystemKeyboard()
{
    DCHECK(keyboard_contents_);
    if (keyboard_contents_->GetURL() != default_url_) {
        // TODO(bshe): The height of system virtual keyboard and IME virtual
        // keyboard may different. The height needs to be restored too.
        LoadContents(default_url_);
    }
}

void KeyboardUIContent::UpdateInsetsForWindow(aura::Window* window)
{
    aura::Window* keyboard_container = keyboard_controller()->GetContainerWindow();
    if (!ShouldWindowOverscroll(window))
        return;

    scoped_ptr<content::RenderWidgetHostIterator> widgets(
        content::RenderWidgetHost::GetRenderWidgetHosts());
    while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
        content::RenderWidgetHostView* view = widget->GetView();
        if (view && window->Contains(view->GetNativeView())) {
            gfx::Rect window_bounds = view->GetNativeView()->GetBoundsInScreen();
            gfx::Rect intersect = gfx::IntersectRects(window_bounds, keyboard_container->bounds());
            int overlap = ShouldEnableInsets(window) ? intersect.height() : 0;
            if (overlap > 0 && overlap < window_bounds.height())
                view->SetInsets(gfx::Insets(0, 0, overlap, 0));
            else
                view->SetInsets(gfx::Insets());
            return;
        }
    }
}

aura::Window* KeyboardUIContent::GetKeyboardWindow()
{
    if (!keyboard_contents_) {
        content::BrowserContext* context = browser_context();
        keyboard_contents_.reset(content::WebContents::Create(
            content::WebContents::CreateParams(context,
                content::SiteInstance::CreateForURL(context,
                    GetVirtualKeyboardUrl()))));
        keyboard_contents_->SetDelegate(new KeyboardContentsDelegate(this));
        SetupWebContents(keyboard_contents_.get());
        LoadContents(GetVirtualKeyboardUrl());
        keyboard_contents_->GetNativeView()->AddObserver(this);
    }

    return keyboard_contents_->GetNativeView();
}

bool KeyboardUIContent::HasKeyboardWindow() const
{
    return !!keyboard_contents_;
}

bool KeyboardUIContent::ShouldWindowOverscroll(aura::Window* window) const
{
    return true;
}

void KeyboardUIContent::ReloadKeyboardIfNeeded()
{
    DCHECK(keyboard_contents_);
    if (keyboard_contents_->GetURL() != GetVirtualKeyboardUrl()) {
        if (keyboard_contents_->GetURL().GetOrigin() != GetVirtualKeyboardUrl().GetOrigin()) {
            // Sets keyboard window rectangle to 0 and close current page before
            // navigate to a keyboard in a different extension. This keeps the UX the
            // same as Android. Note we need to explicitly close current page as it
            // might try to resize keyboard window in javascript on a resize event.
            GetKeyboardWindow()->SetBounds(gfx::Rect());
            keyboard_contents_->ClosePage();
            keyboard_controller()->SetKeyboardMode(FULL_WIDTH);
        }
        LoadContents(GetVirtualKeyboardUrl());
    }
}

void KeyboardUIContent::InitInsets(const gfx::Rect& new_bounds)
{
    // Adjust the height of the viewport for visible windows on the primary
    // display.
    // TODO(kevers): Add EnvObserver to properly initialize insets if a
    // window is created while the keyboard is visible.
    scoped_ptr<content::RenderWidgetHostIterator> widgets(
        content::RenderWidgetHost::GetRenderWidgetHosts());
    while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
        content::RenderWidgetHostView* view = widget->GetView();
        // Can be NULL, e.g. if the RenderWidget is being destroyed or
        // the render process crashed.
        if (view) {
            aura::Window* window = view->GetNativeView();
            if (ShouldWindowOverscroll(window)) {
                gfx::Rect window_bounds = window->GetBoundsInScreen();
                gfx::Rect intersect = gfx::IntersectRects(window_bounds,
                    new_bounds);
                int overlap = intersect.height();
                if (overlap > 0 && overlap < window_bounds.height())
                    view->SetInsets(gfx::Insets(0, 0, overlap, 0));
                else
                    view->SetInsets(gfx::Insets());
                AddBoundsChangedObserver(window);
            }
        }
    }
}

void KeyboardUIContent::ResetInsets()
{
    const gfx::Insets insets;
    scoped_ptr<content::RenderWidgetHostIterator> widgets(
        content::RenderWidgetHost::GetRenderWidgetHosts());
    while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
        content::RenderWidgetHostView* view = widget->GetView();
        if (view)
            view->SetInsets(insets);
    }
    window_bounds_observer_->RemoveAllObservedWindows();
}

void KeyboardUIContent::SetupWebContents(content::WebContents* contents)
{
}

void KeyboardUIContent::OnWindowBoundsChanged(aura::Window* window,
    const gfx::Rect& old_bounds,
    const gfx::Rect& new_bounds)
{
    if (!shadow_) {
        shadow_.reset(new wm::Shadow());
        shadow_->Init(wm::Shadow::STYLE_ACTIVE);
        shadow_->layer()->SetVisible(true);
        DCHECK(keyboard_contents_->GetNativeView()->parent());
        keyboard_contents_->GetNativeView()->parent()->layer()->Add(
            shadow_->layer());
    }

    shadow_->SetContentBounds(new_bounds);
}

void KeyboardUIContent::OnWindowDestroyed(aura::Window* window)
{
    window->RemoveObserver(this);
}

const aura::Window* KeyboardUIContent::GetKeyboardRootWindow() const
{
    if (!keyboard_contents_) {
        return nullptr;
    }
    return keyboard_contents_->GetNativeView()->GetRootWindow();
}

void KeyboardUIContent::LoadContents(const GURL& url)
{
    if (keyboard_contents_) {
        content::OpenURLParams params(
            url,
            content::Referrer(),
            SINGLETON_TAB,
            ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
            false);
        keyboard_contents_->OpenURL(params);
    }
}

const GURL& KeyboardUIContent::GetVirtualKeyboardUrl()
{
    if (keyboard::IsInputViewEnabled()) {
        const GURL& override_url = GetOverrideContentUrl();
        return override_url.is_valid() ? override_url : default_url_;
    } else {
        return default_url_;
    }
}

bool KeyboardUIContent::ShouldEnableInsets(aura::Window* window)
{
    aura::Window* keyboard_window = GetKeyboardWindow();
    return (keyboard_window->GetRootWindow() == window->GetRootWindow() && keyboard::IsKeyboardOverscrollEnabled() && keyboard_window->IsVisible() && keyboard_controller()->keyboard_visible());
}

void KeyboardUIContent::AddBoundsChangedObserver(aura::Window* window)
{
    aura::Window* target_window = window ? window->GetToplevelWindow() : nullptr;
    if (target_window)
        window_bounds_observer_->AddObservedWindow(target_window);
}

} // namespace keyboard
